package com.jl.config;

import com.baomidou.mybatisplus.core.MybatisConfiguration;
import com.baomidou.mybatisplus.extension.plugins.PaginationInterceptor;
import com.zaxxer.hikari.HikariDataSource;
import lombok.AllArgsConstructor;
import org.apache.ibatis.logging.stdout.StdOutImpl;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.shardingsphere.api.config.sharding.ShardingRuleConfiguration;
import org.apache.shardingsphere.api.config.sharding.TableRuleConfiguration;
import org.apache.shardingsphere.api.config.sharding.strategy.InlineShardingStrategyConfiguration;
import org.apache.shardingsphere.api.config.sharding.strategy.StandardShardingStrategyConfiguration;
import org.apache.shardingsphere.api.sharding.standard.PreciseShardingAlgorithm;
import org.apache.shardingsphere.api.sharding.standard.RangeShardingAlgorithm;
import org.apache.shardingsphere.shardingjdbc.api.ShardingDataSourceFactory;
import org.apache.shardingsphere.transaction.core.TransactionType;
import org.apache.shardingsphere.transaction.core.TransactionTypeHolder;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.util.StringUtils;

import javax.sql.DataSource;
import java.lang.reflect.Constructor;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;

@Configuration
@AllArgsConstructor
@MapperScan(basePackages = "**.mapper", sqlSessionFactoryRef = "shardingSqlSessionFactory")
public class JLSharDingDataSourceConfiguration {

    private JLSharDingDataSourceConfig jlSharDingDataSourceConfig;

    private JLMybatisPlusConfigurationProperties jlMybatisPlusConfigurationProperties;

    @Bean(name = "shardingDataSource")
    public DataSource dataSource() throws Exception {
        TransactionTypeHolder.set(TransactionType.LOCAL);
        // 数据源
        Map<String, DataSource> dataSourceMap = new HashMap<>();
        List<JLSharDingDataSourceConfig.DataSource> dataSources = jlSharDingDataSourceConfig.getDataSource();
        StringBuffer nameStr = new StringBuffer();
        for (int i = 0; i < dataSources.size(); i++) {
            JLSharDingDataSourceConfig.DataSource dataSource = dataSources.get(i);
            HikariDataSource hikariDataSource = new HikariDataSource();
            String url = String.format("jdbc:mysql://%s:%d/%s?useUnicode=true&characterEncoding=UTF-8&zeroDateTimeBehavior=convertToNull&allowMultiQueries=true&useSSL=false&allowPublicKeyRetrieval=true",
                    StringUtils.isEmpty(dataSource.getIp()) ? "127.0.0.1" : dataSource.getIp()
                    , dataSource.getPort() == null ? 3306 : dataSource.getPort()
                    , dataSource.getBaseName()
            );
            hikariDataSource.setJdbcUrl(url);
            hikariDataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
            hikariDataSource.setUsername(dataSource.getName());
            hikariDataSource.setPassword(dataSource.getPassword());
            dataSourceMap.put(dataSource.getDataSourceName(), hikariDataSource);
            nameStr.append(dataSource.getDataSourceName() + ",");
        }
        String namestring = nameStr.toString().substring(0, nameStr.toString().length() - 1);
        // 规则
        ShardingRuleConfiguration shardingRuleConfig = new ShardingRuleConfiguration();
        // 配置表规则
        List<JLSharDingDataSourceConfig.Table> tables = jlSharDingDataSourceConfig.getTable();
        for (JLSharDingDataSourceConfig.Table table : tables) {
            StringBuffer stringBuffer = new StringBuffer();
            String[] physicsTable = table.getPhysicsTable().split(",");
            if (StringUtils.isEmpty(table.getDataSourceName())) {
                table.setDataSourceName(namestring);
            }
            String[] dataSourceStr = table.getDataSourceName().split(",");
            for (String s : physicsTable) {
                for (String s1 : dataSourceStr) {
                    String str = s1 + "." + s;
                    stringBuffer.append(str + ",");
                }
            }
            String substring = stringBuffer.toString().substring(0, stringBuffer.toString().length() - 1);
            TableRuleConfiguration tableRuleConfiguration = new TableRuleConfiguration(table.getLogicTable(), substring);
            JLSharDingDataSourceConfig.Table.Rule rule = table.getRule();
            // 分库策略 该字段作为=和in条件查询则检索数据存在的库下所有分片表  （组合分表策略分片字段可实现精确找到库和表查询）
            if (!StringUtils.isEmpty(rule.getBaseRule())) {
                if (rule.getBaseRule().getAlgorithmClass() == null) {
                    tableRuleConfiguration.setDatabaseShardingStrategyConfig(new InlineShardingStrategyConfiguration(rule.getBaseRule().getColumn(), rule.getBaseRule().getAlgorithm()));
                } else {
                    Class<?> claszz = rule.getBaseRule().getAlgorithmClass();
                    Object obj = rule.getBaseRule().getAlgorithmClassValue() != null ? claszz.getConstructor(String.class).newInstance(rule.getBaseRule().getAlgorithmClassValue()) : claszz.newInstance();
                    //= in分片算法
                    PreciseShardingAlgorithm preciseShardingAlgorithm = (PreciseShardingAlgorithm) obj;
                    //BETWEEN AND， >， <，>=，<=分片算法
                    RangeShardingAlgorithm rangeShardingAlgorithm = (RangeShardingAlgorithm) obj;
                    tableRuleConfiguration.setTableShardingStrategyConfig(new StandardShardingStrategyConfiguration(rule.getTableRule().getColumn(), preciseShardingAlgorithm, rangeShardingAlgorithm));
                }
            }
            // 分表策略 该字段作为=和in条件查询则检索所有库、数据存在的分片表  （组合分库策略分片字段可实现精确找到库和表查询）
            if (!StringUtils.isEmpty(rule.getTableRule())) {
                if (rule.getTableRule().getAlgorithmClass() == null) {
                    tableRuleConfiguration.setTableShardingStrategyConfig(new InlineShardingStrategyConfiguration(rule.getTableRule().getColumn(), rule.getTableRule().getAlgorithm()));
                } else {
                    Class<?> claszz = rule.getTableRule().getAlgorithmClass();
                    Object obj;
                    if (rule.getTableRule().getAlgorithmClassValue() != null) {
                        Constructor con = claszz.getConstructor(String.class);
                        obj = con.newInstance(rule.getTableRule().getAlgorithmClassValue());
                    } else {
                        obj = claszz.newInstance();
                    }
                    //= in分片算法
                    PreciseShardingAlgorithm preciseShardingAlgorithm = (PreciseShardingAlgorithm) obj;
                    //BETWEEN AND， >， <，>=，<=分片算法
                    RangeShardingAlgorithm rangeShardingAlgorithm = (RangeShardingAlgorithm) obj;
                    tableRuleConfiguration.setTableShardingStrategyConfig(new StandardShardingStrategyConfiguration(rule.getTableRule().getColumn(), preciseShardingAlgorithm, rangeShardingAlgorithm));
                }
            }
            // 写入规则
            shardingRuleConfig.getTableRuleConfigs().add(tableRuleConfiguration);
        }

        // 属性配置项
        Properties propertie = new Properties();
        // 是否打印SQL解析
        Boolean sqlShow = jlSharDingDataSourceConfig.getSqlShow();
        propertie.setProperty("sql.show", sqlShow == null || !sqlShow ? Boolean.FALSE.toString() : Boolean.TRUE.toString());

        // 获取数据源对象
        DataSource dataSource = ShardingDataSourceFactory.createDataSource(dataSourceMap, shardingRuleConfig, propertie);
        return dataSource;
    }

    @Bean(name = "shardingSqlSessionFactory")
    public SqlSessionFactory sqlSessionFactorySharding(@Qualifier("shardingDataSource") DataSource dataSource) throws Exception {
        SqlSessionFactoryBean sessionFactoryBean = new SqlSessionFactoryBean();
        MybatisConfiguration configuration = new MybatisConfiguration();
        //配置分页
        configuration.addInterceptor(new PaginationInterceptor());
        //配置打印sql语句
        if (jlMybatisPlusConfigurationProperties != null && jlMybatisPlusConfigurationProperties.getLog() != null) {
            configuration.setLogImpl(StdOutImpl.class);
        }
        sessionFactoryBean.setConfiguration(configuration);
        sessionFactoryBean.setDataSource(dataSource);
        return sessionFactoryBean.getObject();
    }

    /**
     * 本地事务
     */
    @Bean(name = "shardingTransactionManagerLOCAL")
    public PlatformTransactionManager transactionManagerLocal(@Qualifier("shardingDataSource") DataSource dataSource) {
        return new DataSourceTransactionManager(dataSource);
    }
}
